/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package proy1sistdistribuidos.modelo.seguridad;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import org.bouncycastle.asn1.x509.Time;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Calendar;
import java.util.Date;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.TBSCertificateStructure;
import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.jce.PrincipalUtil;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
import org.bouncycastle.jce.provider.X509CertificateObject;
import org.bouncycastle.util.Strings;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.bouncycastle.x509.util.LDAPStoreHelper;
import proy1sistdistribuidos.modelo.seguridad.util.X509Util;
//getAlgorithmOID


/**
 *
 * @author jf.garcia50
 */
public class ManejadorCertificadoDigital {

    private X509Certificate cert;
    private RSAPrivateCrtKeyParameters privateKey;
    private boolean esBCAPI;

    /*Metodo para la generacion de los certificados Digitales
     *fuente del metodo: http://www.mayrhofer.eu.org/create-x509-certs-in-java
     */
    public ManejadorCertificadoDigital(String file, String password, String alias, boolean esBCAPI)
            throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
            FileNotFoundException, IOException, UnrecoverableKeyException, InvalidKeyException,
            NoSuchProviderException, SignatureException {

        this.esBCAPI = esBCAPI;

        KeyStore ks = KeyStore.getInstance("PKCS12");//estandar de generacio de llaves

        ks.load(new FileInputStream(new File(file)), password.toCharArray());

        Key key = ks.getKey(alias, password.toCharArray());
        if (key == null) {
            throw new RuntimeException("Llave nula!");
        }
        RSAPrivateCrtKey privKey = (RSAPrivateCrtKey) key;
        privateKey = new RSAPrivateCrtKeyParameters(privKey.getModulus(), privKey.getPublicExponent(), privKey.getPrivateExponent(),
                privKey.getPrimeP(), privKey.getPrimeQ(), privKey.getPrimeExponentP(), privKey.getPrimeExponentQ(), privKey.getCrtCoefficient());


        cert = (X509Certificate) ks.getCertificate(alias);
        if (cert == null) {
            throw new RuntimeException("certificado nulo!");
        }
        cert.verify(cert.getPublicKey());

    }

    /*Metodo para la generacion de los certificados Digitales
     *fuente del metodo: http://www.mayrhofer.eu.org/create-x509-certs-in-java
     *
     */
    public boolean crearCertificado(String dn, int valDias, String expFile, String expPassword) throws
            IOException, InvalidKeyException, SecurityException, SignatureException, 
            NoSuchAlgorithmException, DataLengthException, CryptoException, KeyStoreException,
            NoSuchProviderException, CertificateException, InvalidKeySpecException {
        
        SecureRandom sr = new SecureRandom();

        PublicKey pubKey;
        PrivateKey privKey;

        // generar elkeypair para el nueo certificado
        if (esBCAPI) {
            RSAKeyPairGenerator gen = new RSAKeyPairGenerator();
            gen.init(new RSAKeyGenerationParameters(BigInteger.valueOf(3), sr, 1024, 80));
            AsymmetricCipherKeyPair keypair = gen.generateKeyPair();
            //logger.debug("Generated keypair, extracting components and creating public structure for certificate");
            RSAKeyParameters publicKey = (RSAKeyParameters) keypair.getPublic();
            RSAPrivateCrtKeyParameters pKey = (RSAPrivateCrtKeyParameters) keypair.getPrivate();
            // used to get proper encoding for the certificate
            RSAPublicKeyStructure pkStruct = new RSAPublicKeyStructure(publicKey.getModulus(), publicKey.getExponent());
            
            // JCE format needed for the certificate - because getEncoded() is necessary...
            pubKey = KeyFactory.getInstance("RSA").generatePublic(
                    new RSAPublicKeySpec(publicKey.getModulus(), publicKey.getExponent()));
            // and this one for the KeyStore
            privKey = KeyFactory.getInstance("RSA").generatePrivate(
                    new RSAPrivateCrtKeySpec(publicKey.getModulus(), publicKey.getExponent(),
                    privateKey.getExponent(), privateKey.getP(), privateKey.getQ(),
                    privateKey.getDP(), privateKey.getDQ(), privateKey.getQInv()));
        } else {
            // this is the JSSE way of key generation
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
            keyGen.initialize(1024, sr);
            KeyPair keypair = keyGen.generateKeyPair();
            privKey = keypair.getPrivate();
            pubKey = keypair.getPublic();
        }

        Calendar expiry = Calendar.getInstance();
        expiry.add(Calendar.DAY_OF_YEAR, valDias);

        X509Name x509Name = new X509Name("CN=" + dn);

        V3TBSCertificateGenerator certGen = new V3TBSCertificateGenerator();
        certGen.setSerialNumber(new DERInteger(BigInteger.valueOf(System.currentTimeMillis())));
        certGen.setIssuer(PrincipalUtil.getSubjectX509Principal(cert));
        certGen.setSubject(x509Name);
        DERObjectIdentifier sigOID = X509Util.getAlgorithmOID("SHA1WithRSAEncryption");
        AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(sigOID, new DERNull());
        certGen.setSignature(sigAlgId);
        certGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(
                new ByteArrayInputStream(pubKey.getEncoded())).readObject()));
        certGen.setStartDate(new Time(new Date(System.currentTimeMillis())));
        certGen.setEndDate(new Time(expiry.getTime()));

        // attention: hard coded to be SHA1+RSA!
        SHA1Digest digester = new SHA1Digest();
        AsymmetricBlockCipher rsa = new PKCS1Encoding(new RSAEngine());
        TBSCertificateStructure tbsCert = certGen.generateTBSCertificate();

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        DEROutputStream dOut = new DEROutputStream(bOut);
        dOut.writeObject(tbsCert);

        // firmar
        byte[] signature;
        if (esBCAPI) {
            byte[] certBlock = bOut.toByteArray();
            // first create digest
            digester.update(certBlock, 0, certBlock.length);
            byte[] hash = new byte[digester.getDigestSize()];
            digester.doFinal(hash, 0);
            // firmar
            rsa.init(true, privateKey);
            DigestInfo dInfo = new DigestInfo(new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, null), hash);
            byte[] digest = dInfo.getEncoded(ASN1Encodable.DER);
            signature = rsa.processBlock(digest, 0, digest.length);
        } else {
            // or the JCE way
            PrivateKey caPrivKey = KeyFactory.getInstance("RSA").generatePrivate(
                    new RSAPrivateCrtKeySpec(privateKey.getModulus(), privateKey.getPublicExponent(),
                    privateKey.getExponent(), privateKey.getP(), privateKey.getQ(),
                    privateKey.getDP(), privateKey.getDQ(), privateKey.getQInv()));

            Signature sig = Signature.getInstance(sigOID.getId());
            sig.initSign(caPrivKey, sr);
            sig.update(bOut.toByteArray());
            signature = sig.sign();
        }
        
        // construir la estructura del certificado
        ASN1EncodableVector v = new ASN1EncodableVector();

        v.add(tbsCert);
        v.add(sigAlgId);
        v.add(new DERBitString(signature));

        X509CertificateObject clientCert = new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
        clientCert.verify(cert.getPublicKey());

        // and export as PKCS12 formatted file along with the private key and the CA certificate
        
        PKCS12BagAttributeCarrier bagCert = clientCert;
        bagCert.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
                new DERBMPString("Certificate for IPSec WLAN access"));
        bagCert.setBagAttribute(
                PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
                new SubjectKeyIdentifierStructure(pubKey));

        KeyStore store = KeyStore.getInstance("PKCS12");

        store.load(null, null);

        X509Certificate[] chain = new X509Certificate[2];
        // first the client, then the CA certificate
        chain[0] = clientCert;
        chain[1] = cert;

        store.setKeyEntry("Private key for IPSec WLAN access", privKey, expPassword.toCharArray(), chain);

        FileOutputStream fOut = new FileOutputStream(expFile);

        store.store(fOut, expPassword.toCharArray());

        return true;
    }

    public static void main(String[] args) throws Exception {
		System.out.println(new ManejadorCertificadoDigital("./cert/ca.p12", "procdistribuidos3", "Certificado uniandes", false).crearCertificado("Test CN", 30, "test.p12", "test"));
    }

    /*
    public boolean obtenerLlave() {
        try {
            InputStream inStream = new FileInputStream("fileName");
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
            cert.getPublicKey();






        } catch (Exception e) {
        }
        return true;
    }
     
     */
}
